﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Utility
{
    public class GeometryPoint
    {
        /// <summary>
        /// Gets or sets the x position value  
        /// </summary>
        public double STX { get; set; }

        /// <summary>
        /// Gets or sets the y position value
        /// </summary>
        public double STY { get; set; }

        public GeometryPoint(double x, double y)
        {
            this.STX = x;
            this.STY = y;
        }

        public GeometryPoint() { }
    }

    public class GeometryUtil
    {
        /// <summary>
        /// Convert the gis point to meracatol unit
        /// </summary>
        /// <param name="point">the gis point</param>
        /// <param name="sTX">the converted meracatol x</param>
        /// <param name="sTY">the converted meracatol y</param>
        public static void ConvertToMeracatol(GeometryPoint point, ref double sTX, ref double sTY)
        {
            sTX = point.STX * 20037508.34 / 180.0;
            sTY = Math.Log(Math.Tan((90.0 + point.STY) * Math.PI / 360.0)) / (Math.PI / 180.0);
            sTY = sTY * 20037508.34 / 180.0;
        }

        /// <summary>
        /// Convert the gis point to meracatol unit
        /// </summary>
        /// <param name="point">the gis point</param>
        /// <param name="sTX">the converted meracatol x</param>
        /// <param name="sTY">the converted meracatol y</param>
        public static GeometryPoint ConvertToMeracatol(GeometryPoint point)
        {
            double sTX = 0d;
            double sTY = 0d;

            sTX = point.STX * 20037508.34 / 180.0;
            sTY = Math.Log(Math.Tan((90.0 + point.STY) * Math.PI / 360.0)) / (Math.PI / 180.0);
            sTY = sTY * 20037508.34 / 180.0;

            return new GeometryPoint(sTX, sTY);
        }

        //墨卡托转经纬度
        public static GeometryPoint Mercator2lonLat(GeometryPoint mercator)
        {
            GeometryPoint lonLat = new GeometryPoint();
            double x = mercator.STX / 20037508.34 * 180;
            double y = mercator.STY / 20037508.34 * 180;
            y = 180 / Math.PI * (2 * Math.Atan(Math.Exp(y * Math.PI / 180)) - Math.PI / 2);
            lonLat.STX = x;
            lonLat.STY = y;
            return lonLat;
        }

        /// <summary>
        /// Get distance of two point(gis point which had converted with meracatol regular).
        /// </summary>
        /// <param name="p0x">the p0's Longitude(经度)</param>
        /// <param name="p0y">the p0's Latitude(纬度)</param>
        /// <param name="p1x">the p1's Longitude(经度)</param>
        /// <param name="p1y">the p0's Latitude(纬度)</param>
        /// <returns></returns>
        public static double GetDistanceOfTwoPoints(double p0x, double p0y, double p1x, double p1y)
        {
            double distance = Math.Sqrt((p0x - p1x) * (p0x - p1x) + (p0y - p1y) * (p0y - p1y));

            return distance;
        }

        public static double GetDistanceOfTwoPoints(GeometryPoint start, GeometryPoint end)
        {
            double p0x = start.STX;
            double p0y = start.STY;
            double p1x = end.STX;
            double p1y = end.STY;

            double distance = Math.Sqrt((p0x - p1x) * (p0x - p1x) + (p0y - p1y) * (p0y - p1y));

            return distance;
        }

        /// <summary>
        ///geo_wn_PnPoly(): 测试Point是否在PointList定义的区域内
        ///      Input:   Point, 测试点
        ///               PointList, 测试区域的边界点
        ///      Return:  IsInRegion，true--在范围内，false--不在范围内
        /// </summary>
        /// <param name="pointList"></param>
        /// <param name="Point"></param>
        /// <returns></returns>
        public static bool geo_wn_PnPoly(IList<GeometryPoint> PointList, GeometryPoint Point)
        {
            int n = PointList.Count - 1;
            int wn = 0;    // the winding number counter
            bool IsInRegion = false;

            // loop through all edges of the polygon
            for (int i = 0; i < n; i++)
            {   // edge from V[i] to V[i+1]
                if (PointList[i].STY <= Point.STY)
                {         // start y <= P.y
                    if (PointList[i + 1].STY > Point.STY)      // an upward crossing
                        if (isLeft(PointList[i], PointList[i + 1], Point) > 0)  // P left of edge
                            ++wn;            // have a valid up intersect
                }
                else
                {                       // start y > P.y (no test needed)
                    if (PointList[i + 1].STY <= Point.STY)     // a downward crossing
                        if (isLeft(PointList[i], PointList[i + 1], Point) < 0)  // P right of edge
                            --wn;            // have a valid down intersect
                }
            }

            if (wn != 0)
            {
                IsInRegion = true;
            }

            return IsInRegion;
        }

        /// <summary>
        /// // isLeft(): tests if a point is Left|On|Right of an infinite line.
        //    Input:  three points P0, P1, and P2
        //    Return: >0 for P2 left of the line through P0 and P1
        //            =0 for P2 on the line through P0 and P1
        //            <0 for P2 right of the line through P0 and P1
        /// </summary>
        /// <param name="Point0"></param>
        /// <param name="Point1"></param>
        /// <param name="Point2"></param>
        /// <returns></returns>
        public static double isLeft(GeometryPoint Point0, GeometryPoint Point1, GeometryPoint Point2)
        {
            return ((Point1.STX - Point0.STX) * (Point2.STY - Point0.STY)
                - (Point2.STX - Point0.STX) * (Point1.STY - Point0.STY));
        }

        /// <summary>
        /// 根据 点 和步长绘制栅格四角点
        /// </summary>
        /// <param name="centerPoint">栅格中心点</param>
        /// <param name="gridStep">步长</param>
        /// <returns>四角点</returns>
        public static Tuple<double, double, double, double> GetRectangleByPointAndGridStep(GeometryPoint centerPoint, int gridStep)
        {
            GeometryPoint meracatolPoint = ConvertToMeracatol(centerPoint);

            GeometryPoint lonlatLeftPoint = Mercator2lonLat(new GeometryPoint(meracatolPoint.STX - (gridStep / 2), meracatolPoint.STY - (gridStep / 2)));
            GeometryPoint lonlatRightPoint = Mercator2lonLat(new GeometryPoint(meracatolPoint.STX + (gridStep / 2), meracatolPoint.STY + (gridStep / 2)));

            // item1:MinX,item2:MaxX, item3:MinY ,item4:MaxY
            return new Tuple<double, double, double, double>(lonlatLeftPoint.STX, lonlatRightPoint.STX, lonlatLeftPoint.STY, lonlatRightPoint.STY);
        }

        /// <summary>
        /// 计算 栅格索引
        /// </summary>
        /// <param name="leftUpPoint">大栅格的左上角点(FingerPrintDatabase_Info.minX-lng,FingerPrintDatabase_Info.maxY-lat)</param>
        /// <param name="currentGridPoint">当前栅格点(相当于Atu中的经纬度，米坐标)</param>
        /// <param name="columnCount">FingerPrintDatabase_Info.GridColumnCount</param>
        /// <param name="gridStep">栅格精度(FingerPrintDatabase_Info.GridLength)</param>
        /// <returns>栅格索引</returns>
        public static long ComputeGridIndex(Utility.GeometryPoint leftUpPoint, Utility.GeometryPoint currentPoint, int columnCount, int gridStep)
        {
            int gridYIndex = Convert.ToInt32(Math.Floor((leftUpPoint.STY - currentPoint.STY) / gridStep));
            int gridXIndex = Convert.ToInt32(Math.Floor((currentPoint.STX - leftUpPoint.STX) / gridStep));

            // 当前栅格在整体区域中的索引
            return Convert.ToInt64(gridYIndex) * columnCount + gridXIndex + 1;
        }

        /// <summary>
        /// 计算栅格点的中心点
        /// </summary>
        /// <param name="leftUpPoint"></param>
        /// <param name="currentPoint"></param>
        /// <param name="columnCount"></param>
        /// <param name="gridStep"></param>
        /// <returns></returns>
        public static GeometryPoint ComputeGridCenter(Utility.GeometryPoint leftUpPoint, Utility.GeometryPoint currentPoint, int columnCount, int gridStep)
        {
            int gridYIndex = Convert.ToInt32(Math.Floor((leftUpPoint.STY - currentPoint.STY) / gridStep));
            int gridXIndex = Convert.ToInt32(Math.Floor((currentPoint.STX - leftUpPoint.STX) / gridStep));

            return ComputeGridCenter(leftUpPoint, gridStep, gridYIndex, gridXIndex);
        }

        /// <summary>
        /// 计算栅格点的中心点
        /// </summary>
        /// <param name="leftUpPoint"></param>
        /// <param name="gridStep"></param>
        /// <param name="gridYIndex"></param>
        /// <param name="gridXIndex"></param>
        /// <returns></returns>
        public static GeometryPoint ComputeGridCenter(Utility.GeometryPoint leftUpPoint, int gridStep, int gridYIndex, int gridXIndex)
        {
            // 得到该栅格的左上角(米坐标)
            double gridLeftUpX = leftUpPoint.STX + (gridXIndex * gridStep);
            double gridLeftUpY = leftUpPoint.STY - (gridYIndex * gridStep);

            // 得到该栅格的中心点(米坐标)
            double gridCenterX = gridLeftUpX + ((double)gridStep / 2);
            double gridCenterY = gridLeftUpY - ((double)gridStep / 2);

            GeometryPoint gridCenter = Mercator2lonLat(new GeometryPoint() { STX = gridCenterX, STY = gridCenterY });

            return gridCenter;
        }

    }
}
